/**
 * @file Point.h
 * @brief The class for point of dimension DIM
 * @author XDDDD
 * @version 
 * @date 2021-04-28
 */

#ifndef __PAISLEYPARK__POINT_H__
#define __PAISLEYPARK__POINT_H__

#include <iostream>
#include <initializer_list>
#include <cassert>
#include <cmath>

template<std::size_t DIM>
class Point;

/**
 * @brief Reload the operator <<
 *
 * @tparam DIM The dimension
 * @param  ofstream
 * @param _p The given point
 *
 * @return The ofstream 
 */
template <std::size_t DIM>
std::ostream& operator<<(std::ostream &, const Point<DIM> & _p);

/**
 * @brief Reload the operator +
 *
 * @tparam DIM The dimension
 * @param _p1 The frist point.
 * @param _p2 The second point.
 *
 * @return  p1+p2
 */
template <std::size_t DIM>
Point<DIM> operator+(const Point<DIM> & _p1, const Point<DIM> & _p2);

/**
 * @brief Reload the operator -
 *
 * @tparam DIM The dimension.
 * @param _p1 The first point.
 * @param _p2 The second point.
 *
 * @return p1-p2
 */
template <std::size_t DIM>
Point<DIM> operator-(const Point<DIM> &_p1, const Point<DIM> &_p2);

/**
 * @brief Reload the operator *
 *
 * @tparam DIM The dimension
 * @param _k The number _k.
 * @param _p The point.
 *
 * @return _k*_p
 */
template <std::size_t DIM>
Point<DIM> operator*(const double _k, const Point<DIM> &_p);

template<std::size_t DIM>
class Point {
private:
	double __Coord[DIM];
	double __Time;
public:
	/**
	 * @brief Constructor.
	 */
	Point();
	/**
	 * @brief Constructor.
	 *
	 * @param _x Given list of coordinates of point.
	 */
	Point(const double *_x);
	/**
	 * @brief Constructor.
	 *
	 * @param _p Given points.
	 */
	Point(const Point<DIM> &_p);
	/**
	 * @brief Constructor.
	 *
	 * @param _initl Given initializer list.
	 */
	Point(std::initializer_list<double> _initl);
	/**
	 * @brief Desctructor.
	 */
	~Point(){};
	/**
	 * @brief Set the point. 
	 *
	 * @param _x The given list of coordinates of point.
	 */
	void set_point(const double *_x);
	/**
	 * @brief Set the time.
	 *
	 * @param _t The given time.
	 */
	void set_time(const double _t);
	/**
	 * @brief Get the time.
	 *
	 * @return __Time
	 */
	const double get_time() const;
	/**
	 * @brief Reload the operator []
	 *
	 * @param _n The index.
	 *
	 * @return __Coord[_n] is const.
	 */
	const double& operator[](const std::size_t _n) const;
	/**
	 * @brief Reload the operator []
	 *
	 * @param _n The index.
	 *
	 * @return __Coord[_n], can be changed.
	 */
	double& operator[](const std::size_t _n);
	/**
	 * @brief Reload operator =
	 *
	 * @param _p The given point
	 *
	 * @return This
	 */
	const Point<DIM>& operator=(const Point<DIM> &_p);
	/**
	 * @brief Calculate the 2-norm
	 *
	 * @return 2-norm of the point.
	 */
	const double norm_2() const;
	friend std::ostream& operator<< <>(std::ostream &, const Point<DIM> &);
	friend Point<DIM> operator+ <>(const Point<DIM> &, const Point<DIM> &);
	friend Point<DIM> operator* <>(const double _k, const Point<DIM> &);
	friend Point<DIM> operator- <>(const Point<DIM> &, const Point<DIM> &);
};

template <std::size_t DIM>
Point<DIM>::Point() {
	for(int i = 0; i != DIM; i++) {
		__Coord[i] = 0;
	}
	__Time = 0;
};

template <std::size_t DIM>
Point<DIM>::Point(const double *_x) {
	for(int i = 0; i != DIM; i++) {
		__Coord[i] = _x[i];
	}
	__Time = 0;
};

template <std::size_t DIM>
Point<DIM>::Point(const Point<DIM> &_p) {
	for(int i = 0; i != DIM; i++) {
		__Coord[i] = _p[i];
	}
	__Time = _p.get_time();
};

template <std::size_t DIM>
Point<DIM>::Point(std::initializer_list<double> _initl) {
	assert(_initl.size() == DIM);
	for(int i = 0; i != DIM; i++) {
		__Coord[i] = _initl.begin()[i];
	}
};

template <std::size_t DIM>
void Point<DIM>::set_point(const double *_x) {
	for(int i = 0; i != DIM; i++) {
		__Coord[i] = _x[i];
	}
};

template <std::size_t DIM>
void Point<DIM>::set_time(const double _t) {
	__Time = _t;
};

template <std::size_t DIM>
const double Point<DIM>::get_time() const {
	return __Time;
};

template <std::size_t DIM>
const double& Point<DIM>::operator[](const std::size_t _n) const {
	if(_n < 0 || _n >= DIM) {
		std::cerr << "Out of range!" << std::endl;
		std::exit(-1);
	}
	return __Coord[_n];
};

template <std::size_t DIM>
double& Point<DIM>::operator[](const std::size_t _n) {
	if(_n < 0 || _n >= DIM) {
		std::cerr << "Out of range!" << std::endl;
		std::exit(-1);
	}
	return __Coord[_n];
};

template <std::size_t DIM>
const Point<DIM>& Point<DIM>::operator=(const Point<DIM> &_p) {
	if(this != &_p) {
		for(int i = 0; i != DIM; i++) {
			__Coord[i] = _p[i];
		}
	}
	return *this;
};

template <std::size_t DIM>
const double Point<DIM>::norm_2() const {
	double norm = 0; 
	for(int i = 0; i != DIM; i++) {
		norm = norm + __Coord[i]*__Coord[i];
	}
	norm = sqrt(norm);
	return norm;
};

template <std::size_t DIM>
std::ostream& operator<<(std::ostream &_os, const Point<DIM> &_p) {
	_os << '[';
	for(int i = 0; i != DIM; i++) {
		_os << _p[i];
		if(i != DIM - 1) {
			_os << ", ";
		}
	}
	_os << ']';
};

template <std::size_t DIM>
Point<DIM> operator+(const Point<DIM> &_p1, const Point<DIM> &_p2) {
	Point<DIM> re;
	for(int i = 0; i != DIM; i++) {
		re.__Coord[i] = _p1.__Coord[i] + _p2.__Coord[i]; 
	}
	return re;
};

template <std::size_t DIM>
Point<DIM> operator-(const Point<DIM> &_p1, const Point<DIM> &_p2) {
	Point<DIM> re;
	for(int i = 0; i != DIM; i++) {
		re.__Coord[i] = _p1.__Coord[i] - _p2.__Coord[i]; 
	}
	return re;
}

template <std::size_t DIM>
Point<DIM> operator*(const double _k, const Point<DIM> &_p) {
	Point<DIM> re;
	for(int i = 0; i != DIM; i++) {
		re.__Coord[i] = _p.__Coord[i]*_k;
	}
	return re;
}


#else
//Do nothing.
#endif
